home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / GdmGreeter / language.py < prev    next >
Encoding:
Python Source  |  2013-01-10  |  17.8 KB  |  521 lines

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Copyright 2012 Tails developers <tails@boum.org>
  5. # Copyright 2011 Max <govnototalitarizm@gmail.com>
  6. # Copyright 2011 Martin Owens
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. #  it under the terms of the GNU General Public License as published by
  10. #  the Free Software Foundation, either version 3 of the License, or
  11. #  (at your option) any later version.
  12. #
  13. #  This program is distributed in the hope that it will be useful,
  14. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16. #  GNU General Public License for more details.
  17. #
  18. #  You should have received a copy of the GNU General Public License
  19. #  along with this program.  If not, see <http://www.gnu.org/licenses/>
  20. #
  21. """Localistaion handeling
  22.  
  23. """
  24.  
  25. import logging, gettext, gtk, pycountry, os
  26. import xklavier
  27. import icu
  28.  
  29. from subprocess import Popen, PIPE
  30. from icu import Locale, Collator
  31.  
  32. import GdmGreeter.config
  33.  
  34.  
  35. def ln_cc(lang_name):
  36.     """obtain language code from name
  37.     
  38.     for example: English -> en_US"""
  39.     return _languages_dict[unicode(lang_name)][0]
  40.  
  41. def ln_list(lang_name):
  42.     """obtain list of locales for a given language name
  43.     
  44.     for example: English -> en_US, en_GB"""
  45.     return _languages_dict[unicode(lang_name)]
  46.  
  47. def ln_country(ln_CC):
  48.     """get country name for locale
  49.     
  50.     example: en_US -> USA"""
  51.     return Locale(ln_CC).getDisplayCountry(Locale(ln_CC))
  52.  
  53. def ln_iso639_tri(ln_CC):
  54.     """get iso639 3-letter code
  55.     
  56.     example: en_US -> eng"""
  57.     return Locale(ln_CC).getISO3Language()
  58.  
  59. def ln_iso639_2_T_to_B(ln_CC):
  60.     """Convert a ISO-639-2/T code (e.g. deu for German) to a 639-2/B one (e.g. ger for German)"""
  61.     return pycountry.languages.get(terminology=ln_CC).bibliographic
  62.  
  63. def get_native_langs(lang_list):
  64.     """assemble dictionary of native language names with language codes"""
  65.     langs_dict = {}
  66.     for l in lang_list:
  67.         # English = Locale(en_GB)...
  68.         lang =  Locale(l).getDisplayLanguage(Locale(l)).title()
  69.         try:
  70.             langs_dict[lang]
  71.         except: #XXX specify exception
  72.             langs_dict[lang] = []
  73.         if l not in langs_dict[lang]:
  74.             langs_dict[lang].append(l)
  75.     return langs_dict
  76.  
  77. def get_texts(langs):
  78.     """obtain texts for a given locale using gettext"""
  79.     result = {}
  80.     for k, l in langs.iteritems():
  81.         loc = l[0].split('_')[0]
  82.         try:
  83.             result[str(loc)] = gettext.translation(GdmGreeter.__appname__, GdmGreeter.config.locales_path, [str(loc)])
  84.         except IOError:
  85.             logging.debug('Failed to get texts for %s locale', loc)
  86.     return result
  87.  
  88. def __fill_layouts_dict():
  89.     """assemble dictionary of layout codes to corresponding layout name
  90.     
  91.     We need this dictionnary to get the 'description' (human readeable name)
  92.     of the layout.
  93.     
  94.     Instead of storing correspondance in this dictionnary, we should be able
  95.     to query xklavier with the following code, but it fails because
  96.     `Xkl.ConfigItem.set_name` doesn't exist in python bindings:
  97.     
  98.         _xkl_engine = xklavier.Engine(gtk.gdk.display_get_default())
  99.         _xkl_registry = xklavier.ConfigRegistry(_xkl_engine)
  100.         _xkl_registry.load(False)
  101.         layout_config_item = xklavier.ConfigItem()
  102.         layout_config_item.set_name(layout_code)
  103.         if _xkl_registry.find_layout(layout_config_item):
  104.         return layout_config_item.get_description()
  105.     """
  106.     _xkl_engine = xklavier.Engine(gtk.gdk.display_get_default())
  107.     _xkl_registry = xklavier.ConfigRegistry(_xkl_engine)
  108.     _xkl_registry.load(False)
  109.  
  110.     layouts_dict = {}
  111.  
  112.     def variant_iter(registry, variant, layout):
  113.         code = '%s/%s' % (layout.get_name(), variant.get_name())
  114.         description = '%s - %s' % (layout.get_description(), variant.get_description())
  115.         if code not in layouts_dict:
  116.             layouts_dict[code] = description
  117.  
  118.     def layout_iter(registry, layout, _):
  119.         code = layout.get_name()
  120.         description = layout.get_description()
  121.         if code not in layouts_dict:
  122.             layouts_dict[code] = description
  123.     _xkl_registry.foreach_layout_variant(code, variant_iter, layout)
  124.  
  125.     _xkl_registry.foreach_layout(layout_iter, None)
  126.     return layouts_dict
  127.  
  128. def language_from_locale(locale):
  129.     """Obtain the language code from a locale code
  130.  
  131.     example: fr_FR -> fr"""
  132.     return locale.split('_')[0]
  133.  
  134. def languages_from_locales(locales):
  135.     """Obtain a language code list from a locale code list
  136.  
  137.     example: [fr_FR, en_GB] -> [fr, en]"""
  138.     language_codes = []
  139.     for l in locales:
  140.         language_code = language_from_locale(l)
  141.         if not language_code in language_codes:
  142.             language_codes.append(language_code)
  143.     return language_codes
  144.  
  145. def country_from_locale(locale):
  146.     """Obtain the country code from a locale code
  147.  
  148.     example: fr_FR -> FR"""
  149.     return locale.split('_')[1]
  150.  
  151. def countries_from_locales(locales):
  152.     """Obtain a country code list from a locale code list
  153.  
  154.     example: [fr_FR, en_GB] -> [FR, GB]"""
  155.     country_codes = []
  156.     for l in locales:
  157.         country_code = country_from_locale(l)
  158.         if not country_code in country_codes:
  159.             country_codes.append(country_code)
  160.     return country_codes
  161.  
  162. def language_name(language_code):
  163.     return icu.Locale(language_code).getDisplayLanguage(Locale(language_code)).title()
  164.  
  165. def country_name(locale_code):
  166.     return icu.Locale(locale_code).getDisplayCountry(Locale(locale_code))
  167.  
  168. def layout_name(layout_code):
  169.     if layout_code in _system_layouts_dict:
  170.         return _system_layouts_dict[layout_code]
  171.  
  172. def sort_by_name(list, locale='C'):
  173.     try:
  174.         # Note that we always collate with the 'C' locale.  This is far
  175.         # from ideal.  But proper collation always requires a specific
  176.         # language for its collation rules (languages frequently have
  177.         # custom sorting).  This at least gives us common sorting rules,
  178.         # like stripping accents.
  179.         collator = Collator.createInstance(Locale(locale))
  180.     except:
  181.         collator = None
  182.     def compare_choice(elt):
  183.         """comparison function"""
  184.         if collator:
  185.             try:
  186.                 return collator.getCollationKey(elt[1]).getByteArray()
  187.             except: # Specify exception
  188.                 return elt[1]
  189.     list.sort(key=compare_choice)
  190.     return list
  191.  
  192. def languages_with_names(languages, locale='C'):
  193.     languages_with_names = [(l, language_name(l)) for l in languages]
  194.     sort_by_name(languages_with_names, locale)
  195.     return languages_with_names
  196.  
  197. def locales_with_names(locales, locale='C'):
  198.     locales_with_names = [(l, country_name(l)) for l in locales]
  199.     sort_by_name(locales_with_names, locale)
  200.     return locales_with_names
  201.  
  202. def layouts_with_names(layouts, locale='C'):
  203.     layouts_with_names = [(l, layout_name(l)) for l in layouts]
  204.     sort_by_name(layouts_with_names, locale)
  205.     return layouts_with_names
  206.  
  207. def __get_langcodes():
  208.     with open(GdmGreeter.config.language_codes_path, 'r') as f:
  209.         langcodes = [ line.rstrip('\n') for line in f.readlines() ]
  210.     logging.debug('%s languages found', len(langcodes))
  211.     return langcodes
  212.  
  213. class TranslatableWindow(object):
  214.     """Interface providing functions to translate a window on the fly
  215.     """
  216.     retain_focus = True
  217.  
  218.     def __init__(self, window):
  219.         self.window = window
  220.         self.labels = []
  221.         self.tips = []
  222.         self.store_translations(self.window)
  223.         self.__texts = get_texts(_languages_dict)
  224.  
  225.     def store_translations(self, widget):
  226.         """Go through all widgets and store the translatable elements"""
  227.         for child in widget.get_children():
  228.             if isinstance(child, gtk.Container):
  229.                 self.store_translations(child)
  230.             if isinstance(child, gtk.Label):
  231.                 self.labels.append( (child, child.get_label()) )
  232.             if child.get_has_tooltip():
  233.                 self.tips.append( (child, child.get_tooltip_text()) )
  234.  
  235.     def language(self, lang):
  236.         """Return normalised language for use in this process"""
  237.         if '_' in lang:
  238.             lang = lang.split('_')[0]
  239.         if '.' in lang:
  240.             lang = lang.split('.')[0]
  241.         return lang.lower()
  242.  
  243.     def gettext(self, lang, text):
  244.         """Return a translated string or string"""
  245.         if lang:
  246.             text = lang.gettext(text)
  247.         return text
  248.  
  249.     def translate_to(self, lang):
  250.         """Loop through everything and translate on the fly"""
  251.         lang = self.__texts.get(self.language(lang), None)
  252.         for (child, text) in self.labels:
  253.             child.set_label(self.gettext(lang, text))
  254.         for (child, text) in self.tips:
  255.             child.set_tooltip_markup(self.gettext(lang, text))
  256.         if self.window.get_sensitive() and self.window.get_visible() and self.retain_focus:
  257.             self.window.present()
  258.  
  259. class LocalisationSettings(object):
  260.     """Model storing settings related to language and keyboard
  261.  
  262.     """
  263.     def __init__(self, greeter):
  264.         self._greeter = greeter
  265.  
  266.         self._xkl_engine = xklavier.Engine(gtk.gdk.display_get_default())
  267.         self._xkl_registry = xklavier.ConfigRegistry(self._xkl_engine)
  268.         self._xkl_registry.load(False)
  269.         self._xkl_record = xklavier.ConfigRec()
  270.         self._xkl_record.get_from_server(self._xkl_engine)
  271.  
  272.         self._system_locales_list = langcodes
  273.         self._system_locales_dict = self.__fill_locales_dict(self._system_locales_list)
  274.  
  275.         self._language = 'en'
  276.         self._locale = 'en_US'
  277.         self._layout = 'us'
  278.         self._variant = ''
  279.         self._options = 'grp:alt_shift_toggle'
  280.  
  281.         self.set_language('en')
  282.         self.set_locale('en_US')
  283.         self.set_layout('us')
  284.  
  285.     def __fill_locales_dict(self, locales):
  286.         """assemble dictionary of language codes to corresponding locales list
  287.         
  288.         example {en: [en_US, en_GB], ...}"""
  289.         locales_dict = {}
  290.         for locale in locales:
  291.             # English = Locale(en_GB)...
  292.             lang = language_from_locale(locale)
  293.             if lang not in locales_dict:
  294.                 locales_dict[lang] = []
  295.             if locale not in locales_dict[lang]:
  296.                 locales_dict[lang].append(locale)
  297.         return locales_dict
  298.  
  299.     def __apply_layout_to_upcoming_session(self):
  300.         self._greeter.SelectLayout(self._layout)
  301.         if self._layout != 'us':
  302.             layout = '%s,us' % self._layout
  303.             variant = '%s,' % self._variant
  304.         else:
  305.             layout = self._layout
  306.             variant = self._variant
  307.         with open(GdmGreeter.config.locale_output_path, 'w') as f:
  308.             os.chmod(GdmGreeter.config.locale_output_path, 0o600)
  309.             f.write('TAILS_LOCALE_NAME=%s\n' % self._locale)
  310.             f.write('TAILS_XKBMODEL=%s\n' % 'pc105') # use default value from /etc/default/keyboard
  311.             f.write('TAILS_XKBLAYOUT=%s\n' % layout)
  312.             f.write('TAILS_XKBVARIANT=%s\n' % variant)
  313.             f.write('TAILS_XKBOPTIONS=%s\n' % self._options)
  314.  
  315.     # LANGUAGES
  316.  
  317.     def get_languages(self):
  318.         """Return a list of available languages codes
  319.  
  320.         """
  321.         return languages_from_locales(self._system_locales_list)
  322.  
  323.     def get_languages_with_names(self):
  324.         return languages_with_names(self.get_languages(), self.get_locale())
  325.  
  326.     def get_default_languages(self):
  327.         """Return a list of default languages codes
  328.  
  329.         """
  330.         return languages_from_locales(GdmGreeter.config.default_locales)
  331.  
  332.     def get_default_languages_with_names(self):
  333.         return languages_with_names(self.get_default_languages(), self.get_locale())
  334.  
  335.     def get_language(self):
  336.         """Return current language code
  337.  
  338.         """
  339.         return self._language
  340.  
  341.     def set_language(self, language):
  342.         self._language = language
  343.         self.__set_default_locale()
  344.  
  345.     # LOCALES
  346.  
  347.     def get_locales(self):
  348.         """Return a list of all available locale codes
  349.  
  350.         XXX: not useful? impossible to set country w/o corresponding language
  351.         """
  352.         return self._system_locales_list
  353.  
  354.     def get_default_locales(self):
  355.         """Return available locales for current language
  356.  
  357.         """
  358.         lang = self._language
  359.         if lang in self._system_locales_dict:
  360.             return self._system_locales_dict[lang]
  361.  
  362.     def get_default_locales_with_names(self):
  363.         return locales_with_names(self.get_default_locales(), self.get_locale())
  364.  
  365.     def get_locale(self):
  366.         return self._locale
  367.  
  368.     def set_locale(self, locale):
  369.         self._locale = locale
  370.         self.__apply_locale()
  371.         self.__set_default_layout()
  372.  
  373.     def __set_default_locale(self):
  374.         """Set default locale for current language
  375.  
  376.         Select locale whose country code corresponds to language code. If none,
  377.         select first locale.
  378.         """
  379.         default_locale = None
  380.         default_locales = self.get_default_locales()
  381.         logging.debug("default_locales = %s" % default_locales)
  382.         if default_locales: 
  383.             for locale in default_locales:
  384.                 if ((locale == 'en_US')
  385.                     or (language_from_locale(locale).lower() ==
  386.                         country_from_locale(locale).lower())):
  387.                     default_locale = locale
  388.             if not default_locale:
  389.                 default_locale = default_locales[0]
  390.         else:
  391.             default_locale = 'en_US'
  392.         logging.debug("setting default locale to %s" % default_locale)
  393.         self.set_locale(default_locale)
  394.  
  395.     def __apply_locale(self):
  396.         self._greeter.SelectLanguage(self._locale)
  397.  
  398.     # LAYOUTS
  399.  
  400.     def get_layouts(self):
  401.         """Return a list of all available keyboard layout codes
  402.  
  403.         """
  404.         layouts = _system_layouts_dict.keys()
  405.         return layouts
  406.  
  407.     def get_layouts_with_names(self):
  408.         return layouts_with_names(self.get_layouts(), self.get_locale())
  409.  
  410.     def layouts_for_language(self, lang):
  411.         layouts = []
  412.         t_code = ln_iso639_tri(self._language)
  413.  
  414.         def language_iter(config_registry, item, subitem, store):
  415.             layout_code = item.get_name()
  416.             if layout_code not in layouts:
  417.                 layouts.append(item.get_name())
  418.  
  419.         self._xkl_registry.foreach_language_variant(t_code,
  420.                                                     language_iter,
  421.                                                     layouts)
  422.         if len(layouts) == 0:
  423.             b_code = ln_iso639_2_T_to_B(t_code)
  424.             logging.debug(
  425.                 'got no layout for ISO-639-2/T code %s, trying with ISO-639-2/B code %s',
  426.                 t_code, b_code)
  427.             self._xkl_registry.foreach_language_variant(b_code,
  428.                                                         language_iter,
  429.                                                         layouts)
  430.  
  431.         logging.debug('got %d layouts for %s', len(layouts), self._language)
  432.         return layouts
  433.  
  434.     def get_default_layouts(self):
  435.         """Return list of supported keyboard layouts for current language
  436.         
  437.         """
  438.  
  439.         layouts = self.layouts_for_language(self._language)
  440.         if not layouts:
  441.             layouts = self.layouts_for_language(country_from_locale(self._locale).lower())
  442.         if not layouts:
  443.             layouts = ['us']
  444.         return layouts
  445.  
  446.     def get_default_layouts_with_names(self):
  447.         return layouts_with_names(self.get_default_layouts(), self.get_locale())
  448.  
  449.     def get_layout(self):
  450.         return self._layout
  451.  
  452.     def set_layout(self, layout):
  453.         try:
  454.             layout, variant = layout.split('/')
  455.         except ValueError:
  456.             layout = layout
  457.             variant = ''
  458.         self._layout = layout
  459.         self._variant = variant
  460.         self.__apply_layout_to_current_screen()
  461.         self.__apply_layout_to_upcoming_session()
  462.  
  463.     def __set_default_layout(self):
  464.         default_layout = False
  465.         backup_layout = False
  466.         default_layouts = self.get_default_layouts()
  467.         for layout_code in default_layouts:
  468.             logging.debug("layout_code=%s, ln=%s, CC=%s" % 
  469.                 (layout_code,
  470.                  language_from_locale(self._locale).lower(),
  471.                  country_from_locale(self._locale).lower()))
  472.             if language_from_locale(self._locale).lower() == layout_code:
  473.                 default_layout = layout_code
  474.             elif country_from_locale(self._locale).lower() == layout_code:
  475.                 backup_layout = layout_code
  476.         if not default_layout:
  477.             if backup_layout:
  478.                 default_layout = backup_layout
  479.             elif len(default_layouts) > 0:
  480.                 default_layout = self.get_default_layouts()[0]
  481.             else:
  482.                 default_layout = 'us'
  483.         self.set_layout(default_layout)            
  484.  
  485.     def __apply_layout_to_current_screen(self):
  486.         logging.debug("layout=%s" % self._layout)
  487.  
  488.         if self._layout != 'us':
  489.             layout_list = ['us', self._layout]
  490.             variant_list = ['', self._variant]
  491.         else:
  492.             layout_list = [self._layout]
  493.             variant_list = [self._variant]
  494.  
  495.         self._xkl_record.set_layouts(layout_list)
  496.         self._xkl_record.set_variants(variant_list)
  497.         # 'grp:sclk_toggle' would be much more convenient but we default to
  498.         # mustdie switcher in here
  499.         self._xkl_record.set_options([self._options])
  500.         self._xkl_record.activate(self._xkl_engine)
  501.         # try to 'enforce layout'
  502.         self._xkl_engine.start_listen(xklavier.XKLL_TRACK_KEYBOARD_STATE)
  503.         self._xkl_engine.lock_group(1)
  504.         self._xkl_engine.stop_listen(xklavier.XKLL_TRACK_KEYBOARD_STATE)
  505.  
  506.         logging.debug('L:%s V:%s O:%s',
  507.                        self._xkl_record.get_layouts(),
  508.                        self._xkl_record.get_variants(),
  509.                        self._xkl_record.get_options())
  510.  
  511. # MODULE INITIALISATION
  512.  
  513. # List of system locale codes
  514. langcodes = __get_langcodes()
  515.  
  516. # dictionnary of native language: language code
  517. _languages_dict = get_native_langs(langcodes)
  518.  
  519. # dictionnary of layout codes: layout name
  520. _system_layouts_dict = __fill_layouts_dict()
  521.